BUT-Info/S3/R3.04 (Qualité)

Suivre ces slides …​

http://bit.ly/jmb-cpoa
http://bit.ly/jmb-cpoa
http://iut-blagnac.github.io/cpoa/

Introduction

Ce cours porte sur la Conception et Programmation Objet Avancée.

Concepts objets

Vous avez appris (cf. R2.03 et R2.04) un certain nombre de concepts objets :

  • Abstraction

  • Encapsulation

  • Héritage

  • Polymorphisme

Abstraction

Définition :

Abstraction

Définition (restrictive) :

Une classe est une abstraction des caractéristiques communes d’un ensemble d’objets.

Encapsulation

Définition :

Encapsulation

Définition (restrictive) :

Dans la description d’un objet, le but de l'encapsulation est de masquer les attributs et les méthodes, c’est-à-dire, la manière dont est réalisé le comportement de l’objet.

Héritage

Définition :

Héritage

Définition (simpliste) :

L’héritage est la transmission de caractéristiques à ses descendants.

La classe qui hérite dispose des méthodes et attributs de niveau public et protected de sa classe mère.

Polymorphisme

Polymorphisme

Le nom de polymorphisme vient du grec :

qui peut prendre plusieurs formes

L’héritage concerne les classes, le polymorphisme concerne les objets.

Polymorphisme (suite)

On distingue généralement trois types de polymorphisme :

  • Le polymorphisme ad hoc (également surcharge ou en anglais overloading)

  • Le polymorphisme paramétrique (également généricité ou en anglais template)

  • Le polymorphisme d’héritage (également redéfinition, spécialisation ou en anglais overriding)

Polymorphisme ad hoc

  • Appelé aussi surcharge.

  • Permet d’avoir des fonctions de même nom dans des classes sans aucun rapport entre elles.

  • Permet de définir des opérateurs d’utilisation différente en fonction des paramètres.

int method(int,int);
int method(int);
int method(float,float);

Le polymorphisme paramétrique

Appelé aussi généricité.

interface Iterator<E> {
    boolean hasNext();
    E next();
}

public <T> static void copy(Collection<? extends T> source, Collection<? super T> dest) {
    for (T t : source) {
         dest.add(t);
    }
}

Le polymorphisme d’héritage

  • Appelé aussi spécialisation (ou redéfinition).

  • Lié à la redéfinition des méthodes héritées.

Objectifs de la conception objet

Eviter trois problèmes principaux :

La rigidité

Anticiper les évolutions

La fragilité

Eviter les erreurs dues aux modifications

L’immobilité

Rendre plus facile la réutilisation

Bonnes pratiques et patrons

Pour répondre aux problèmes ci-dessus, on va s’attaquer à diminuer les dépendances et éviter l'"effet spaghetti".

Les qualités recherchées sont :

  • Robustesse : les changements n’introduisent pas de régressions.

  • Extensibilité : il est facile d’ajouter de nouvelles fonctionnalités.

  • Réutilisabilité : il est possible de réutiliser certaines parties de code pour construire d’autres applications.

Bonnes pratiques et patrons (suite)

Nous allons apprendre des bonnes pratiques :

  • Identifier les aspects qui varient et les séparer des aspects constants

  • Programmer une interface, non une implémentation

  • Préférer la composition à l’héritage

Bonnes pratiques et patrons (suite)

Nous allons apprendre des bonnes pratiques :

  • Les classes doivent être ouvertes à l’extension, mais fermées à la modification

  • Dépendez d’abstractions. Ne dépendez pas de classes concrètes (inversion des dépendances)

  • Ne parlez pas aux inconnus

Bonnes pratiques et patrons (suite)

L’étape suivante consiste à apprendre les bonnes solutions de conception, ce qu’on appelle les patrons de conception (ou design patterns en anglais).

Rappels sur des éléments Java importants

Importance du typage

Différents types de typage

Le fait d’attribuer un type (une classe) à une variable (un objet) peut se faire de plusieurs façons :

  • statique

  • dynamique

  • duck typing

Typage statique

On parle de typage statique quand la majorité des vérifications de type sont effectuées au moment de la compilation.

Exemple de typage statique
int i = 0;  // cette déclaration indique explicitement que
            // la variable i est de type entier

Typage dynamique

Le typage dynamique consiste à laisser l’ordinateur réaliser l’opération de typage à la volée, lors de l’exécution du code.

Typage dynamique (exemple)

Exemple de typage dynamique
/**
 * @author André Peninou
 */
public class Type {
  void m() {
    System.out.println ("Type");
    }
}
public class SousType extends Type {
  void m() {
    System.out.println ("SousType");
  }
  void autreM(){
    System.out.println ("Spécifique SousType");
  }
}
...
    Type a = new Type();
    a.m(); // "Type"

    a = new SousType();
    a.m(); // "SousType"
    // Statique : a est un Type (à la compil)
    // Dynamique : a est un SousType au runtime.

    // D'où :
    a = new SousType();
    a.autreM();
    // NOK car type statique == A => autreM() n'existe pas à la compilation
...

Duck typing

Style de typage dynamique où la sémantique d’un objet (c’est-à-dire son type) est déterminée par l’ensemble de ses méthodes et de ses attributs, et non par un type défini et nommé explicitement par le programmeur.

L’origine de cette expression est liée à cette citation :

Si je vois un animal qui vole comme un canard, cancane comme un canard, et nage comme un canard, alors j’appelle cet oiseau un canard.

— James Whitcomb Riley

Duck typing (exemple)

Exemple de duck typing en Ruby
def calcule(a, b, c)
  return a*b+c
end

$a = calcule(6, 3, 2)
$b = calcule('6', 3, ', the number of the beast')

puts $a.to_s
puts $b.to_s

Ce qui donne :

20
666, the number of the beast

Introduction : importance des patrons

phdComics

Science is what we understand well enough to explain to a computer. Art is everything else we do.

— Donald Knuth

Strategy

Principes de conception

Principe de conception

Identifiez les aspects de votre code qui varient et séparez-les de ceux qui demeurent constant.

Principes de conception (suite)

Principe de conception

Programmer une interface, non une implémentation.

Principes de conception (suite)

Principe de conception

Préférez la composition à l’héritage.

Définition du patron

Design pattern : Stratégie (Strategy)

Stratégie définit une famille d’algorithmes, encapsule chacun d’eux et les rend interchangeables. Il permet à l’algorithme de varier indépendamment des clients qui l’utilisent.

strategy

Premier exemple d’utilisation

superCanardFinal a
superCanardFinal b
superCanardFinal c
Question
Pourquoi n’a-t’on pas utilisé Strategy pour afficher() ou nager()?

Autre exemple concret

L’exemple qui suit est tiré de ce cours.

Le problème

Vous avez une classe FileWriter qui a pour rôle d’écrire dans un fichier ainsi qu’une classe DBWriter. Dans un premier temps, ces classes ne contiennent qu’une méthode write() qui n’écrira que le texte passé en paramètre.

Au fil du temps, vous vous rendez compte que c’est dommage qu’elles ne fassent que ça et vous aimeriez bien qu’elles puissent écrire en différents formats (HTML, XML, etc.) : les classes doivent donc formater puis écrire.

Autre exemple concret (suite)

La solution

strategyWriter

Autre exemple concret (suite)

L’interface en PHP (code source ici)
<?php
interface Formater
{
  public function format($text);
}
?>

Autre exemple concret (suite)

La classe abstraite Writer (code source ici)
<?php
abstract class Writer
{
  // Attribut contenant l'instance du formateur que l'on veut utiliser.
  protected $formater;

  abstract public function write($text);

  // Nous voulons une instance d'une classe implémentant Formater en paramètre.
  public function __construct(Formater $formater)
  {
    $this->formater = $formater;
  }
}
?>

Autre exemple concret (suite)

La classe FileWriter (code source ici)
<?php
class FileWriter extends Writer
{
  // Attribut stockant le chemin du fichier.
  protected $file;

  public function __construct(Formater $formater, $file)
  {
    parent::__construct($formater);
    $this->file = $file;
  }

  public function write($text)
  {
    $f = fopen($this->file, 'w');
    fwrite($f, $this->formater->format($text));
    fclose($f);
  }
}
?>

Autre exemple concret (suite)

La classe DBWriter (code source ici)
<?php
class DBWriter extends Writer
{
  protected $db;

  public function __construct(Formater $formater, PDO $db)
  {
    parent::__construct($formater);
    $this->db = $db;
  }

  public function write ($text)
  {
    $q = $this->db->prepare('INSERT INTO lorem_ipsum SET text = :text');
    $q->bindValue(':text', $this->formater->format($text));
    $q->execute();
  }
}
?>

Autre exemple concret (suite)

Enfin, nous avons nos trois formateurs. L’un ne fait rien de particulier (TextFormater), et les deux autres formatent le texte en deux langages différents (HTMLFormater et XMLFormater).

Autre exemple concret (suite)

La classe TextFormater (code source ici)
<?php
class TextFormater implements Formater
{
  public function format($text)
  {
    return 'Date : ' . time() . "\n" . 'Texte : ' . $text;
  }
}
?>

Autre exemple concret (suite)

La classe HTMLFormater (code source ici)
<?php
class HTMLFormater implements Formater
{
  public function format($text)
  {
    return '<p>Date : ' . time() . '<br />' ."\n". 'Texte : ' . $text . '</p>';
  }
}
?>

Autre exemple concret (suite)

La classe XMLFormater (code source ici)
<?php
class XMLFormater implements Formater
{
  public function format($text)
  {
    return '<?xml version="1.0" encoding="ISO-8859-1"?>' ."\n".
           '<message>' ."\n".
           "\t". '<date>' . time() . '</date>' ."\n".
           "\t". '<texte>' . $text . '</texte>' ."\n".
           '</message>';
  }
}
?>

D’autres exemples

  • La fonction standard sort() de python

    >>> sorted("This is a test string from Andrew".split(), key=str.lower)
    ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
  • Stratégie de cryptage en fonction de la taille d’un fichier

    File file = getFile();
     Cipher c = CipherFactory.getCipher( file.size() );
     c.performAction();
    
    // implementations:
    interface  Cipher  {
         public void performAction();
    }
    class InMemoryCipherStrategy implements Cipher {
             public void performAction() {
                 // load in byte[] ....
             }
    }
    class SwaptToDiskCipher implements Cipher {
             public void performAction() {
                 // swapt partial results to file.
             }
    }
Plus de détails ici

(non) Réutilisation

Les patrons ne sont pas réutilisables!

Il faut implémeter la solution qu’il représente à chaque fois.

Réutilisation (exception)

Exception : certains font l’objet d’une librairie (comme Observer de Java).

Par exemple le patron Singleton existe dans la bibliothèque standard du langage en Ruby. C’est un mixin qu’il suffit d’inclure dans la classe qui doit être un singleton.

class Klass
   include Singleton
   # ...
end

a,b  = Klass.instance, Klass.instance

a == b
# => true

Klass.new
# => NoMethodError - new is private ...

Association ou composition

On trouve deux modèles UML™ :

strategy compo
strategy assoc

Association ou composition (suite)

Et donc deux implémentations :

Composition ⇒ le composé encapsule les composants
public class Colvert extends Canard {

	protected Colvert() {
		this(new VolerAvecDesAiles(), new Cancan());
	}
...
c1 = new Colvert();
Association ⇒ le composant existe "en dehors"
...
vol = new VolerAvecDesAiles();
cri = new Cancan();
c1 = new Colvert(vol,cri);
...

Un peu d’histoire

1977

Alexander : patterns pour les architectures (les vraies)

Alexander
1987

Beck et Cunningham : patterns pour des interfaces utilisateurs

1988

Meyer : livre sur l’orienté objet (langage Eiffel), devenu la bible pour beaucoup de programmeurs (cf. [Meyer88])

1990-1995

Gamma, Helm, Johnson et Vlissides : LE livre de référence (cf. [GoF])

GoF
Les auteurs de ce livre sont connus comme les Gof pour « Gang of Four ».
2003

Martin : principes SOLID (cf. [Martin03])

2004

Craig Larman décrit des modèles de conception : les Patterns GRASP (cf. [Larman05])

Exemples de bons principes

SOLID:

  • Single Responsibility Principle

  • Open-Closed Principle

  • Liskov Substitution Principle

  • Interface Segregation Principle

  • Dependency Inversion Principle

Single Responsibility Principle

solid s
Responsabilité => Sujet à changement

Open-Closed Principle

solid o
Ouvert à l'extension mais fermé à la modification

Open-Closed Principle (suite)

Ainsi, une fois écrite et testée, une classe ne devrait être modifiée que pour être corrigée! Toute modification devrait être possible par extension.

Liskov Substitution Principle

liskov

Barbara Liskov est toujours active!

liskov2018

LSP : le principe

solid l
Une classe doit pouvoir être remplacée par une instance d'un de ses
sous-types, sans modifier la cohérence du programme

LSP : un exemple

Un carré est un rectangle particulier.

carre
Question
Peut-on toujours substituer un Carré à la place d’un Rectangle ?

Vraiment?

Réponse (Rectangle.java)
class Rectangle
{
	protected int m_width;
	protected int m_height;

	public void setWidth(int width){
		m_width = width;
	}

	public void setHeight(int height){
		m_height = height;
	}


	public int getWidth(){
		return m_width;
	}

	public int getHeight(){
		return m_height;
	}

	public int getArea(){
		return m_width * m_height;
	}
}

Vraiment?

Réponse (Square.java)
// Violation of Likov's Substitution Principle
class Square extends Rectangle
{
	public void setWidth(int width){
		m_width = width;
		m_height = width;
	}

	public void setHeight(int height){
		m_width = height;
		m_height = height;
	}

}

Vraiment?

Réponse (Square.java - suite)
class LspTest
{
	private static Rectangle getNewRectangle()
	{
		// it can be an object returned by some factory ...
		return new Square();
	}

	public static void main (String args[])
	{
		Rectangle r = LspTest.getNewRectangle();

		r.setWidth(5);
		r.setHeight(10);
		// User knows that r is a rectangle.
		// It assumes that he's able to set the width and height as for the base class

		System.out.println(r.getArea());
		// Now she's surprised to see that the area is 100 instead of 50.
	}
}

Et l’inverse?

rectangle

Même problème

Réponse (Rectangle.java)
class LspTest
{
	private static Square getNewSquare()
	{
		// it can be an object returned by some factory ...
		return new Rectangle();
	}

	public static void main (String args[])
	{
		Square s = LspTest.getNewSquare();

		s.setWidth(5);
		// User knows that r is a rectangle.
		// It assumes that he's able to set the width and height as for the base class

		System.out.println(s.getArea());
		// Now she's surprised to see that the area is 0 instead of 25.
	}
}

Interface Segregation Principle

solid i
Préférer plusieurs interfaces spécifiques pour chaque client plutôt qu'une seule interface générale

Dependency Inversion Principle

solid d
Il faut dépendre des abstractions, pas des implémentations

DIP : explications

Ce principe indique :

  • Les modules de haut niveau (abstraits) ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions.

  • Les abstractions ne doivent pas dépendre des détails d’implémentation. C’est l’inverse : les détails doivent dépendre des abstractions.

Ainsi ce principe va à l’encontre de l’intuition classique.

Exemple (bad)

dip bad
Figure 1. Exemple de code violant le principe d’inversion des dépendances

Exemple (good)

dip
Figure 2. Exemple de code ne violant plus le principe d’inversion des dépendances

SOLID et patrons

QUESTION

Lesquels des 5 principes SOLID s’appliquent bien à Strategy ?

solid s

solid o

solid l

solid i

solid d

SOLID et patrons (éléments de réponses)

GRASP

The critical design tool for software development is a mind well educated in design principles. It is not the UML or any other technology.

2005
— Craig Larman

Il s’agit d’un ensemble de patrons, plutôt orientés conception (UML). Nous en aborderons certains au travers des exemples de ce module (cf. [Larman05]).

Notez que les principes SOLID ne s’appliquent pas qu’à la programmation objet. Pour une discussion sur leur application avec React (language fonctionnel), cf. https://dev.to/shadid12/can-you-apply-solid-principles-to-your-react-applications-46il.

Les patrons : comment ça marche ?

Intérêt

  • Réponses éprouvées à des problèmes récurrents

  • Vocabulaire commun

T’as qu’à utiliser une factory!

Patrons abordés

Patrons non abordés

  • Décorateur

  • Commande

  • Façade

  • Patron de méthode

  • Chaînes de responsabilité

  • Prototype

  • Mémento

  • Médiateur

  • Interprète

  • Poids-mouche

  • Monteur

  • Pont

About…​

Document réalisé par Jean-Michel Bruel via Asciidoctor (version 2.0.23) de 'Dan Allen', lui même basé sur AsciiDoc. Pour l’instant ce document est libre d’utilisation et géré par la 'Licence Creative Commons'. Licence Creative Commons licence Creative Commons Paternité - Partage à l'Identique 3.0 non transposé.